package cn.krui.helper.tools;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.ss.util.WorkbookUtil;

/**
 * @author zc
 */
public class ExcelContext {
  private boolean autoGenerateHeaders;
  
  private Workbook book;
  
  private Sheet sheet;
  
  private String sheetName;
  
  private int sheetIndex;
  
  private int originColIndex;
  
  private int originRowIndex;
  
  private int colPointer;
  
  private int rowPointer;
  
  private int maxRows;
  
  private IExcelSheetNameFormatter sheetNameFmt;
  
  private ExcelColumn[][] columns;
  
  private IExcelOriginPostionHandler postionHandler;
  
  private IExcelSheetMaxRowsHandler maxRowsHandler;
  
  private Map<String, CellStyle> styles;
  
  private IExcelRowStyleHandler rowStyleHandler;
  
  private List<ExcelColumn> columnList;
  
  private List<List<ExcelColumn>> column2Dlist;
  
  private boolean needGenerateHeaders = true;
  
  private short[] headerHieght;
  
  CreationHelper factory;
  
  Drawing<?> drawing;
  
  public static final String DEFAULT_HEADER_STYLE_NAME = "default.header.style.name";
  
  public static final String DEFAULT_CELL_STYLE_NAME = "default.cell.style.name";
  
  public static final String DEFAULT_ROW_STYLE_NAME = "default.row.style.name";
  
  public static final String DEFAULT_DATE_STYLE_NAME = "default.date.style.name";
  
  public ExcelContext(Workbook book, IExcelSheetNameFormatter sheetNameFmt, ExcelColumn[][] columns, IExcelRowStyleHandler rowStyleHandler, IExcelOriginPostionHandler postionHandler, IExcelSheetMaxRowsHandler maxRowsHandler, Map<String, CellStyle> styles, boolean autoGenerateHeaders, short[] headerHieght) {
    this.sheetIndex = 0;
    this.book = book;
    this.columns = columns;
    this.sheetNameFmt = sheetNameFmt;
    this.rowStyleHandler = rowStyleHandler;
    this.postionHandler = postionHandler;
    this.maxRowsHandler = maxRowsHandler;
    this.autoGenerateHeaders = autoGenerateHeaders;
    this.columnList = new ArrayList<>();
    this.styles = styles;
    this.headerHieght = headerHieght;
    this.factory = book.getCreationHelper();
    if (this.columns != null) {
      this.column2Dlist = new ArrayList<>();
      int r;
      for (r = 0; r < this.columns.length; r++){
        this.column2Dlist.add(new ArrayList<>()); 
      }
      for (r = 0; r < this.columns.length; r++) {
        ExcelColumn[] row = this.columns[r];
        for (int c = 0; c < row.length; c++) {
          ExcelColumn ec = row[c];
          ((List<ExcelColumn>)this.column2Dlist.get(r)).add(ec);
        } 
      } 
      for (int i = 0; i < this.column2Dlist.size(); i++) {
        List<ExcelColumn> row = this.column2Dlist.get(i);
        for (int j = 0; j < row.size(); j++) {
          ExcelColumn ec = row.get(j);
          if (ec != null){
            for (int k = 0; k < ec.getRowSpan(); k++) {
              for (int c = 0; c < ec.getColSpan(); c++) {
                if (k != 0 || c != 0){
                  ((List)this.column2Dlist.get(i + k)).add(j + c, (Object)null); 
                }
              } 
            }  
          }
        } 
      } 
      List<ExcelColumn> li = new ArrayList<>();
      for (List<ExcelColumn> row : this.column2Dlist) {
        for (int c = 0; c < row.size(); c++) {
          if (c >= li.size()){
            li.add((ExcelColumn)null); 
          }
          if (row.get(c) != null){
            li.set(c, row.get(c)); 
          }
        } 
      } 
      li.removeIf(e -> (e == null));
      this.columnList = li;
    } 
    builder();
  }
  
  void writeRows(int rowIndex, Map<String, Object> data) {
    if (rowIndex < 0) {
      for (ExcelColumn col : this.columnList) {
        if (col.isAutoSize()){
          this.sheet.autoSizeColumn(getColPointer(), true); 
        }
        nextColPointer(col.getColSpan());
      } 
      resetColPointer();
      return;
    } 
    if (data == null){
      return; 
    }
    if (this.columnList == null || this.columnList.size() == 0) {
      this.columnList = new ArrayList<>();
      for (String field : data.keySet()) {
        ExcelColumn ec = new ExcelColumn(field, field);
        this.columnList.add(ec);
      } 
    } 
    if (this.needGenerateHeaders) {
      generateHeaders(0, this.columnList);
      this.needGenerateHeaders = false;
    } 
    Row row = this.sheet.getRow(this.rowPointer);
    if (row == null){
      row = this.sheet.createRow(this.rowPointer); 
    }
    if (this.rowStyleHandler != null) {
      ExcelRowStyle rowStyle = this.rowStyleHandler.getStyle(rowIndex, data);
      if (rowStyle != null) {
        row.setHeight(rowStyle.getHeight());
        if (rowStyle.getStyle() != null){
          row.setRowStyle(rowStyle.getStyle()); 
        }
      } 
    } else {
      CellStyle cellStyle = this.styles.get("default.row.style.name");
      if (cellStyle != null){
        row.setRowStyle(cellStyle); 
      }
    } 
    Integer rowSpan = null;
    for (ExcelColumn col : this.columnList) {
      CellStyle cellStyle;
      Object value = data.get(col.getField());
      Cell cell = row.getCell(getColPointer());
      if (cell == null){
        cell = row.createCell(getColPointer()); 
      }
      IExcelCellFormatter fmt = col.getCellFormatter();
      ValueSpan span = null;
      if (fmt != null){
        span = fmt.formatter(value, rowIndex, data, col); 
      }
      if (span == null){
        span = new ValueSpan(value, 1, 1); 
      }
      IExcelCellStyleHandler cellStyleHandler = col.getCellStyleHandler();
      if (cellStyleHandler != null) {
        cellStyle = this.styles.get(cellStyleHandler.getStyle(rowIndex, value, data));
        if (cellStyle != null){
          cell.setCellStyle(cellStyle); 
        }
      } else {
        cellStyle = this.styles.get(col.getCellStyleName());
        if (cellStyle == null){
          if (span.getValue() instanceof java.util.Date) {
            cellStyle = this.styles.get("default.date.style.name");
          } else {
            cellStyle = this.styles.get("default.cell.style.name");
          }
        }
        if (col.getDataFormat() != null && col.getDataFormat().length() > 0) {
          if (cellStyle == null){
            cellStyle = this.book.createCellStyle(); 
          }
          DataFormat dataFmt = this.book.createDataFormat();
          cellStyle.setDataFormat(dataFmt.getFormat(col.getDataFormat()));
        } 
        if (cellStyle != null){
          cell.setCellStyle(cellStyle); 
        }
      } 
      if (span.getFormula() != null && span.getFormula().length() > 0) {
        cell.setCellFormula(span.getFormula());
      } else {
        ExcelHelper.writeToCell(cell, span.getValue());
      } 
      if (span.getError() != null){
        cell.setCellErrorValue(span.getError().byteValue()); 
      }
      if (span.getCommentAuthor() != null || span.getCommentBody() != null) {
        ClientAnchor anchor = this.factory.createClientAnchor();
        anchor.setCol1(getColPointer());
        anchor.setCol2(getColPointer() + span.getColSpan() - 1);
        anchor.setRow1(getRowPointer());
        anchor.setRow2(getRowPointer() + span.getRowSpan() - 1);
        Comment comment = this.drawing.createCellComment(anchor);
        comment.setAuthor(span.getCommentAuthor());
        comment.setString(this.factory.createRichTextString(span.getCommentBody()));
        cell.setCellComment(comment);
      } 
      Row rows = row;
      for (int r = 0; r < span.getRowSpan(); r++) {
        for (int c = 0; c < span.getColSpan(); c++) {
          if (r != 0 || c != 0){
            if (rows.getCell(getColPointer() + c) == null){
              rows.createCell(getColPointer() + c);  
            }
          }
        } 
        if (r > 0) {
          rows = this.sheet.getRow(getRowPointer() + r);
          if (rows == null){
            rows = this.sheet.createRow(getRowPointer() + r); 
          }
        } 
      } 
      if (span.getColSpan() > 1 || span.getRowSpan() > 1) {
        CellRangeAddress region = new CellRangeAddress(getRowPointer(), 
            getRowPointer() + span.getRowSpan() - 1, getColPointer(), 
            getColPointer() + span.getColSpan() - 1);
        this.sheet.addMergedRegion(region);
        if (cellStyle != null) {
          RegionUtil.setBorderTop(cellStyle.getBorderTop(), region, this.sheet);
          RegionUtil.setTopBorderColor(cellStyle.getTopBorderColor(), region, this.sheet);
          RegionUtil.setBorderRight(cellStyle.getBorderRight(), region, this.sheet);
          RegionUtil.setRightBorderColor(cellStyle.getRightBorderColor(), region, this.sheet);
          RegionUtil.setBorderBottom(cellStyle.getBorderBottom(), region, this.sheet);
          RegionUtil.setBottomBorderColor(cellStyle.getBottomBorderColor(), region, this.sheet);
          RegionUtil.setBorderLeft(cellStyle.getBorderLeft(), region, this.sheet);
          RegionUtil.setLeftBorderColor(cellStyle.getLeftBorderColor(), region, this.sheet);
        } 
      } 
      if (rowSpan == null || span.getRowSpan() < rowSpan.intValue()){
        rowSpan = Integer.valueOf(span.getRowSpan()); 
      }
      nextColPointer(span.getColSpan());
    } 
    nextRowPointer(rowSpan.intValue());
    resetColPointer();
    nextSheet();
  }
  
  void builder() {
    String name = "Sheet" + (this.sheetIndex + 1);
    if (this.sheetNameFmt != null){
      name = this.sheetNameFmt.formatter(this.sheetIndex); 
    }
    String safeSheetName = WorkbookUtil.createSafeSheetName(name);
    this.sheetName = safeSheetName;
    if (this.postionHandler != null) {
      ExcelOriginPostion postion = this.postionHandler.getExcelOriginPostion(this.sheetIndex);
      this.originColIndex = postion.getColIndex();
      this.originRowIndex = postion.getRowIndex();
    } else {
      this.originColIndex = 0;
      this.originRowIndex = 0;
    } 
    if (this.maxRowsHandler != null) {
      this.maxRows = this.maxRowsHandler.getMaxRows(this.sheetIndex);
    } else {
      this.maxRows = -1;
    } 
    this.sheet = this.book.getSheet(this.sheetName);
    if (this.sheet == null){
      this.sheet = this.book.createSheet(this.sheetName); 
    }
    this.drawing = this.sheet.createDrawingPatriarch();
    resetColPointer();
    resetRowPointer();
    if (this.column2Dlist != null) {
      int index = 0;
      for (List<ExcelColumn> headerRow : this.column2Dlist){
        generateHeaders(index++, headerRow); 
      }
      this.needGenerateHeaders = false;
    } else if (this.autoGenerateHeaders) {
      this.needGenerateHeaders = true;
    } else {
      this.needGenerateHeaders = false;
    } 
  }
  
  void generateHeaders(int rowIndex, List<ExcelColumn> headers) {
    Row rows = this.sheet.getRow(getRowPointer());
    if (rows == null){
      rows = this.sheet.createRow(getRowPointer()); 
    }
    if (this.headerHieght != null && this.headerHieght.length > rowIndex){
      rows.setHeight(this.headerHieght[rowIndex]); 
    }
    for (ExcelColumn headerCel : headers) {
      if (headerCel == null) {
        nextColPointer(1);
        continue;
      } 
      int[] widths = headerCel.getWidth();
      if (widths != null) {
        if (widths.length == 0){
          break; 
        }
        int col = getColPointer();
        for (int i = 0; i < widths.length; i++) {
          if (widths[i] > -1){
            this.sheet.setColumnWidth(col + i, widths[i]); 
          }
        } 
      } 
      if (headerCel.isAutoSize()){
        this.sheet.autoSizeColumn(getColPointer(), true); 
      }
      Cell cell = rows.getCell(getColPointer());
      if (cell == null){
        cell = rows.createCell(getColPointer()); 
      }
      CellStyle cellStyle = headerCel.getHeaderStyle();
      if (cellStyle == null){
        cellStyle = this.styles.get(headerCel.getHeaderStyleName()); 
      }
      if (cellStyle == null){
        cellStyle = this.styles.get("default.header.style.name"); 
      }
      if (cellStyle != null){
        cell.setCellStyle(cellStyle); 
      }
      ExcelHelper.writeToCell(cell, headerCel.getTitle());
      Row row = rows;
      for (int r = 0; r < headerCel.getRowSpan(); r++) {
        for (int c = 0; c < headerCel.getColSpan(); c++) {
          if (r != 0 || c != 0){
            if (row.getCell(getColPointer() + c) == null){
              row.createCell(getColPointer() + c);  
            }
          }
        } 
        if (r > 0) {
          row = this.sheet.getRow(getRowPointer() + r);
          if (row == null){
            row = this.sheet.createRow(getRowPointer() + r); 
          }
        } 
      } 
      if (headerCel.getColSpan() > 1 || headerCel.getRowSpan() > 1) {
        CellRangeAddress region = new CellRangeAddress(getRowPointer(), 
            getRowPointer() + headerCel.getRowSpan() - 1, getColPointer(), 
            getColPointer() + headerCel.getColSpan() - 1);
        this.sheet.addMergedRegion(region);
        RegionUtil.setBorderTop(cellStyle.getBorderTop(), region, this.sheet);
        RegionUtil.setTopBorderColor(cellStyle.getTopBorderColor(), region, this.sheet);
        RegionUtil.setBorderRight(cellStyle.getBorderRight(), region, this.sheet);
        RegionUtil.setRightBorderColor(cellStyle.getRightBorderColor(), region, this.sheet);
        RegionUtil.setBorderBottom(cellStyle.getBorderBottom(), region, this.sheet);
        RegionUtil.setBottomBorderColor(cellStyle.getBottomBorderColor(), region, this.sheet);
        RegionUtil.setBorderLeft(cellStyle.getBorderLeft(), region, this.sheet);
        RegionUtil.setLeftBorderColor(cellStyle.getLeftBorderColor(), region, this.sheet);
      } 
      nextColPointer(1);
    } 
    nextRowPointer(1);
    resetColPointer();
  }
  
  public void nextColPointer(int step) {
    this.colPointer += step;
  }
  
  public void nextRowPointer(int step) {
    this.rowPointer += step;
  }
  
  public void resetColPointer() {
    this.colPointer = this.originColIndex;
  }
  
  public void resetRowPointer() {
    this.rowPointer = this.originRowIndex;
  }
  
  public boolean nextSheet() {
    if (this.maxRows > 0 && this.maxRows >= this.rowPointer) {
      this.sheetIndex++;
      builder();
      return true;
    } 
    return false;
  }
  
  public Workbook getBook() {
    return this.book;
  }
  
  public void setBook(Workbook book) {
    this.book = book;
  }
  
  public Sheet getSheet() {
    return this.sheet;
  }
  
  public void setSheet(Sheet sheet) {
    this.sheet = sheet;
  }
  
  public String getSheetName() {
    return this.sheetName;
  }
  
  public void setSheetName(String sheetName) {
    this.sheetName = sheetName;
  }
  
  public int getSheetIndex() {
    return this.sheetIndex;
  }
  
  public void setSheetIndex(int sheetIndex) {
    this.sheetIndex = sheetIndex;
  }
  
  public int getOriginColIndex() {
    return this.originColIndex;
  }
  
  public void setOriginColIndex(int originColIndex) {
    this.originColIndex = originColIndex;
  }
  
  public int getOriginRowIndex() {
    return this.originRowIndex;
  }
  
  public void setOriginRowIndex(int originRowIndex) {
    this.originRowIndex = originRowIndex;
  }
  
  public int getColPointer() {
    return this.colPointer;
  }
  
  public void setColPointer(int colPointer) {
    this.colPointer = colPointer;
  }
  
  public int getRowPointer() {
    return this.rowPointer;
  }
  
  public void setRowPointer(int rowPointer) {
    this.rowPointer = rowPointer;
  }
  
  public int getMaxRows() {
    return this.maxRows;
  }
  
  public void setMaxRows(int maxRows) {
    this.maxRows = maxRows;
  }
  
  public boolean isAutoGenerateHeaders() {
    return this.autoGenerateHeaders;
  }
  
  public void setAutoGenerateHeaders(boolean autoGenerateHeaders) {
    this.autoGenerateHeaders = autoGenerateHeaders;
  }
  
  public IExcelSheetNameFormatter getSheetNameFmt() {
    return this.sheetNameFmt;
  }
  
  public void setSheetNameFmt(IExcelSheetNameFormatter sheetNameFmt) {
    this.sheetNameFmt = sheetNameFmt;
  }
  
  public ExcelColumn[][] getColumns() {
    return this.columns;
  }
  
  public void setColumns(ExcelColumn[][] columns) {
    this.columns = columns;
  }
  
  public IExcelOriginPostionHandler getPostionHandler() {
    return this.postionHandler;
  }
  
  public void setPostionHandler(IExcelOriginPostionHandler postionHandler) {
    this.postionHandler = postionHandler;
  }
  
  public IExcelSheetMaxRowsHandler getMaxRowsHandler() {
    return this.maxRowsHandler;
  }
  
  public void setMaxRowsHandler(IExcelSheetMaxRowsHandler maxRowsHandler) {
    this.maxRowsHandler = maxRowsHandler;
  }
  
  public IExcelRowStyleHandler getRowStyleHandler() {
    return this.rowStyleHandler;
  }
  
  public void setRowStyleHandler(IExcelRowStyleHandler rowStyleHandler) {
    this.rowStyleHandler = rowStyleHandler;
  }
  
  public boolean isNeedGenerateHeaders() {
    return this.needGenerateHeaders;
  }
  
  public void setNeedGenerateHeaders(boolean needGenerateHeaders) {
    this.needGenerateHeaders = needGenerateHeaders;
  }
  
  public short[] getHeaderHieght() {
    return this.headerHieght;
  }
  
  public void setHeaderHieght(short[] headerHieght) {
    this.headerHieght = headerHieght;
  }
}
